<template>
    <view :class="show">
        <view class="xy__layout-container" :style="onholdStyle">
            <view v-for="(item, i) in newLayout" :key="i">
                <!-- 本地 -->

                <view v-if="item.isPusher" class="video" :style="item.style" :data-item="item" @tap.stop.prevent="onClickContent" @longpress="onLongPress">
                    <live-pusher
                        v-if="pushUrl"
                        :beauty="beauty"
                        :whiteness="whiteness"
                        class="video-player"
                        :device-position="devicePosition"
                        :enable-camera="camera && isPushed"
                        id="pusher"
                        :muted="mutedClone"
                        :enable-mic="true"
                        :enable-ans="true"
                        :enable-agc="true"
                        :background-mute="false"
                        mode="RTC"
                        :url="pushUrl"
                        :autopush="false"
                        :debug="debug"
                        :waiting-image="waitingImage"
                        @netstatus="onPusherNetStatus"
                        @statechange="onPusherStateChange"
                        @error="onPusherError"
                        @audiovolumenotify="onPusherAudioVolumeNotify"
                    />
                    <view class="video-status" v-if="pushUrl && isPushed">
                        <image
                            :src="mutedClone ? 'https://sdk-source.xylink.com/miniProgram/audio_mute.png' : 'https://sdk-source.xylink.com/miniProgram/audio_unmute.png'"
                            class="video-mute-icon"
                        />
                        <view class="video-member">{{ item.name }}</view>
                    </view>
                    <view class="video-pause-bg" v-else>
                        <image :src="backgroundImage" class="video-pause-bg" />
                        <view class="video-pause-box">{{ !!item.stateText ? item.stateText.wating : '视频请求中' }}</view>
                    </view>
                    <view class="video-pause-bg" v-if="!camera">
                        <image :src="backgroundImage" class="video-pause-bg" />
                        <view class="video-pause-box">{{ !!item.stateText ? item.stateText.videoMute : '视频关闭' }}</view>
                    </view>
                </view>

                <!-- 远端 -->

                <view v-else class="video" :style="item.style" :data-item="item" @tap.stop.prevent="onClickContent" @longpress="onLongPress">
                    <live-player
                        v-if="item.playUrl"
                        :id="item.id"
                        :data-streamid="item.playUrl"
                        :data-item="item"
                        class="video-player"
                        mode="RTC"
                        :muted="item.muted"
                        :object-fit="item.fit ? item.fit : 'fillCrop'"
                        :src="item.playUrl"
                        :autoplay="true"
                        aspect="16:9"
                        :debug="debug"
                        :auto-pause-if-navigate="false"
                        :auto-pause-if-open-native="false"
                        @statechange="onPlayStateChange"
                        @netstatus="onPlayNetStatus"
                        @audiovolumenotify="onPlayAudioVolumeNotify"
                    />
                    <view class="video-status">
                        <image
                            :src="
                                item.roster.audioTxMute && !item.roster.isContent
                                    ? 'https://sdk-source.xylink.com/miniProgram/audio_mute.png'
                                    : 'https://sdk-source.xylink.com/miniProgram/audio_unmute.png'
                            "
                            class="video-mute-icon"
                        />
                        <view class="video-member">{{ item.name }}</view>
                    </view>
                    <!-- 正常播放情况 -->
                    <view class="video-cover-view" v-if="item.playUrl && !item.roster.videoTxMute && item.status === 'start'" />
                    <!-- 视频关闭 -->
                    <!-- 正在清流 -->
                    <view class="video-pause-bg" v-else>
                        <image :src="backgroundImage" class="video-pause-bg" />
                        <view class="video-pause-box" v-if="item.roster.videoTxMute">{{ !!item.stateText ? item.stateText.videoMute : '视频暂停' }}</view>
                        <view class="video-pause-box" v-else>{{ !!item.stateText ? item.stateText.wating : '视频请求中' }}</view>
                    </view>
                </view>
            </view>
        </view>
    </view>
</template>

<script>
/**
 * 小程序sdk <xylink-room>组件
 *
 * ！！！此组件是通过SDK非UI模式实现，整体流程可以作为开发者参考非UI模式的实现细节
 *
 * @authors Luo-jinghui、Chengfei (luojinghui424@gmail.com)
 * @date  2018-11-27 15:59:48
 */

import regeneratorRuntime from './utils/runtime'; // eslint-disable-line
import { computedBehavior } from './utils/computed';
import XYRTC from '@/utils/xy/xy-mp-sdk';
export default {
    data() {
        return {
            // 是否显示<xylink-room />组件
            display: false,

            // Layout布局列表数据
            layout: [],

            // 是否移入等候室
            onHold: false,

            // 是否已经启动推流
            isPushed: false,

            pushUrl: '',
            mutedClone: false,
            templateClone: {}
        };
    },
    mixins: [computedBehavior],
    props: {
        // 美颜级别
        beauty: {
            type: Number,
            default: 5
        },
        // 美白级别
        whiteness: {
            type: Number,
            default: 5
        },
        // 是否关闭麦克风，默认false开启
        muted: {
            type: Boolean,
            default: false
        },
        // 是否自动调用开启/关闭摄像头方法，默认是true，开启，false代表关闭，则需要业务上自行调用unmuteVideo/muteVideo方法
        mutedAtuoOperate: {
            type: Boolean,
            default: true
        },
        // 是否关闭摄像头，默认false关闭
        camera: {
            type: Boolean,
            default: false
        },
        // 设置前/后置摄像头
        devicePosition: {
            type: String,
            default: 'front'
        },
        // 挂起图片
        waitingImage: {
            type: String,
            default: 'https://sdk-source.xylink.com/miniProgram/waitingImage.png'
        },
        // 背景图片
        backgroundImage: {
            type: String,
            default: 'https://sdk-source.xylink.com/miniProgram/meeting_bg_720.jpg'
        },
        // 自定义布局，动态改变模版
        template: {
            type: Object,

            default: () => ({
                layout: 'auto',
                detail: []
            })
        },
        // 是否开启调试模式
        debug: {
            type: Boolean,
            default: false
        }
    },
    computed: {
        show() {
            return this.display ? 'xy-room' : 'xy-room xy-hide';
        },
        newLayout() {
            let layoutMode = 'auto';
            if (this.XYClient) {
                layoutMode = this.XYClient.getLayoutMode();
            }
            const layout = this.layout.map((item) => {
                let name = item.name;
                let newStyle = item.style || '';

                // 【UI模式】自动布局下，针对Content下的语音激励人员标记篮框
                if (layoutMode === 'auto') {
                    // 自动布局使用的是Roster中的displayName
                    name = item.roster.displayName;
                    if (this.content.userId && item.roster.isActiveSpeaker && !item.roster.audioTxMute) {
                        newStyle = newStyle.concat(`border: 2px solid #44b5ff; box-sizing: border-box;`);
                    } else {
                        // 否则清理篮框标记
                        newStyle = newStyle.concat(`border: none; box-sizing: border-box;`);
                    }
                }

                // 画面填充模式为图像铺满屏幕，避免出现黑边问题
                const fit = 'fillCrop';
                const muted = !!item.roster.muted;
                const newItem = Object.assign({}, item, {
                    fit,
                    muted,
                    name,
                    style: newStyle
                });
                return newItem;
            });
            return layout;
        },
        onholdStyle() {
            return this.onHold ? 'position: fixed; visibility: hidden;' : '';
        }
    },
    mounted() {
        // 处理小程序 attached 生命周期
        this.attached();
        // 处理小程序 ready 生命周期
        this.$nextTick(() => this.ready());
    },
    // 在组件实例被从页面节点树移除时执行
    destroyed() {
        console.log('[component] page detached');
        // 组件销毁时，掉用SDK挂断会议方法
        this.XYClient.hangup();
    },
    created() {
        // 是否开启了权限设置提示
        this.isShowDetected = false;
        // 缓存共享Content的数据
        this.content = {};

        // xyRTC初始化
        this.initRTCEvent();
    },
    methods: {
        // 在组件实例进入页面节点树时执行
        attached() {
            console.log('[component] page attached');
        },

        // 在组件在视图层布局完成后执行
        ready() {
            console.log('[component] page ready');
        },

        /**
         * 【UI组件】参会者画面单击、双击画面事件
         *
         * @param { IRoster } params - 参会者画面信息
         */
        onClickContent(params) {
            const currentTime = params.timeStamp;
            const lastTapTime = this.lastTapTime;
            this.lastTapTime = currentTime;
            if (currentTime - lastTapTime < 250) {
                this.onFullScreenContent(params);
                this.postEvent('eventDoubleClick', params);
                clearTimeout(this.lastTapTimeoutFunc);
            } else {
                this.lastTapTimeoutFunc = setTimeout(() => {
                    this.postEvent('eventClick', params);
                }, 250);
            }
        },

        /**
         * 【UI组件】参会者画面长按事件
         *
         * @param { IRoster } params - 参会者画面信息
         */
        onLongPress(params) {
            console.log('onLongPress:', params);
            this.postEvent('eventLongPress', params);
        },

        /**
         * 【UI组件】全屏显示画面
         *
         * @param { * } params - 切换全屏画面
         */
        onFullScreenContent(params) {
            console.log('[component] onFullScreenContent:', params);
            const item = params.currentTarget.dataset.item;
            this.XYClient.handleFullScreen(item);
        },

        /**
         * 【UI组件】静音/取消静音操作
         *
         * @public
         */
        onSwitchAudioMute() {
            console.log('[component] onSwitchAudioMute muted:', this.mutedClone);
            if (!this.XYClient) {
                console.log('rejected switch audio mute');
                return;
            }

            // 根据组件推送的最新状态，调用接口，通知远端
            if (this.mutedClone) {
                this.XYClient.muteAudio();
            } else {
                this.XYClient.unmuteAudio();
            }
        },

        /**
         * 开启/关闭视频
         *
         * @public
         */
        onSwitchVideoMute() {
            console.log('[component] onSwitchAudioMute camera:', this.camera);
            if (!this.XYClient || !this.mutedAtuoOperate) {
                console.log('rejected switch video mute: ', this.mutedAtuoOperate);
                return;
            }

            // 根据组件推送的最新状态，调用接口，通知远端
            if (this.camera) {
                this.XYClient.unmuteVideo();
            } else {
                this.XYClient.muteVideo();
            }
        },

        /**
         * 【UI组件】通过<xylink-room />组件上报事件消息
         *
         * @param { string } type - 事件type，具有唯一性
         * @param { any } detail - 事件详情
         */
        postEvent(type, detail) {
            this.$emit('onRoomEvent', {
                detail: {
                    type,
                    detail
                }
            });
        },

        /**
         * 监听XYRTC Client实例上的事件
         */
        initRTCEvent() {
            this.XYClient = XYRTC.createClient();

            // UI组件实例，内部会掉用此实例上的挂断方法
            // 注意：非UI模式下，开发者不需要调用此方法
            this.XYClient.setComponentInstance(this);
            // 注意：非UI模式下需要调用，将页面实例也SDK绑定，内部创建推拉流实例使用
            this.XYClient.setPageInstance(this);

            // 默认关闭摄像头入会，发送视频关闭通知
            this.onSwitchVideoMute();
            // 默认开启麦克风入会，发送麦克风开启通知
            this.onSwitchAudioMute();
            this.XYClient.on('roomEvent', (e) => {
                const { type, detail } = e;
                this.postEvent(type, detail);
                this.showComponent();
                // 非UI模式，直接通过XYClient监听事件即可，不需要通过组件上报
                switch (type) {
                    case 'connected':
                        // 入会成功事件
                        console.log('[component] connected message');
                        break;
                    case 'onHold':
                        // 等候室事件
                        console.log('[component] onHold message:', detail);
                        const status = detail;
                        this.setData({
                            onHold: !!status
                        });
                        break;
                    case 'pushUrl':
                        // 获取到推流地址事件
                        console.log('[component] pushUrl message:', detail);
                        this.setData(
                            {
                                pushUrl: detail
                            },
                            // 切记，需要等待推流地址设置完成后，再启动本地推流
                            () => {
                                this.XYClient.startLivePusher(
                                    () => {
                                        console.log('[component]start pusher success');

                                        // 非UI模式下（自建），修复iOS开启摄像头入会后再关闭摄像头，远端听不到小程序声音问题
                                        // 目的是初始设置camera为关闭状态，等到推流启动后，再开启摄像头
                                        this.setData({
                                            isPushed: true
                                        });
                                    },
                                    (err) => {
                                        console.log('start pusher failed', err, 'warn');
                                    }
                                );
                            }
                        );
                        break;
                    case 'permission':
                        // 权限被拒异常处理
                        console.log('[component] permission message:', detail);
                        // UI模式下，用户第一次拒绝授权麦克风/摄像头权限，需要引导用户授权处理
                        // 非UI模式下，开发者可自行选择是否需要处理权限拒绝情况
                        this.detectAuthorizeModel();
                        break;
                    case 'layout':
                        // 自动布局/自定义布局上报布局结果，基于此数据渲染画面
                        console.log('[component] layout message:', detail);
                        this.setData({
                            layout: detail
                        });
                        break;
                    case 'content':
                        // 共享Content数据
                        console.log('[component] content message:', detail);
                        this.content = detail;
                        break;
                    case 'audioStatus':
                        // 本地实时麦克风状态
                        console.log('[component] audioStatus message:', detail);
                        this.setData({
                            mutedClone: detail
                        });
                        break;
                    default:
                        break;
                }
            });
        },

        /**
         * 挂断会议，清理组件的Template临时数据
         *
         * 注意：非UI模式下，需主动调用XYClient实例上的hangup方法
         *
         * @public
         */
        hangup() {
            console.log('[component] hangup meeting');
            this.resetTemplate();
        },

        // 切换摄像头
        async switchCamera() {
            console.log('[component] switchCamera');
            return await this.XYClient.switchCamera();
        },

        // 显示组件
        showComponent() {
            this.setData({
                display: true
            });
        },

        /**
         *【非UI模式必须调用】绑定live-player状态事件
         *
         * @param { * } e - live-player状态事件
         */
        onPlayStateChange(e) {
            this.XYClient.playerEventHandler(e);
            this.postEvent('playerStatusChange', e);
        },

        /**
         *【非UI模式必须调用】绑定live-pusher状态事件
         *
         * @param { * } e - live-pusher状态事件
         */
        onPusherStateChange(e) {
            this.XYClient.pusherEventHandler(e);
            this.postEvent('roomChange', e.detail);
        },

        /**
         *【非UI模式必须调用】绑定live-pusher网络状态数据
         *
         * @param { * } e -
         */
        onPusherNetStatus(e) {
            this.XYClient.pusherNetStatusHandler(e);
        },

        /**
         *【非UI模式必须调用】绑定live-player网络状态数据
         *
         * @param { * } e - live-pusher网络状态数据事件
         */
        onPlayNetStatus(e) {
            this.XYClient.playNetStatusHandler(e);
        },

        /**
         *【非UI模式必须调用】绑定live-pusher渲染错误事件
         *
         * @param { * } e - live-pusher推流失败事件
         */
        onPusherError(e) {
            this.XYClient.pusherErrorHandler(e);
            console.warn('[component]pusher other error: ', errCode);
        },

        /**
         *【非UI模式必须调用】绑定麦克风采集的音量大小事件
         *
         * @param { * } e - live-pusher麦克风音量大小事件
         */
        onPusherAudioVolumeNotify(e) {
            this.XYClient.pusherAudioVolumeNotify(e);
        },

        /**
         *【非UI模式必须调用】绑定播放音量大小事件
         *
         * @param { * } e - live-player组件播放音量大小事件
         */
        onPlayAudioVolumeNotify(e) {
            this.XYClient.playAudioVolumeNotify(e);
        },

        /**
         * 重置template
         */
        resetTemplate() {
            console.log('[component] resetTemplate');
            let template = {
                layout: 'auto',
                detail: []
            };
            if (this.templateClone && JSON.stringify(this.templateClone) !== '{}') {
                template = this.templateClone;
            }
            this.setData({
                templateClone: template
            });
        },

        /**
         * 授权状态更新，需要重新入会
         *
         * @param { string } code - 错误码
         * @param { string } msg - 错误提示
         * @param { object } data - 数据
         */
        exitRoom(key, msg = '', data = null) {
            const response = {
                code: 'ROOM_EXIT',
                key,
                message: msg,
                data
            };
            console.log('[component]exit room, send disconnected msg: ', response);
            this.postEvent('disconnected', response);
        },

        /**
         * 权限确认提示框
         *
         * UI组件模式下代为处理
         * 非UI模式下，需自行在业务上选择处理
         *
         * @private
         */
        detectAuthorizeModel() {
            if (!this.isShowDetected) {
                this.isShowDetected = true;
                uni.showModal({
                    title: '权限提示',
                    content: '请开启“麦克风”和“摄像头”权限才可以进行音视频通话',
                    showCancel: true,
                    cancelText: '取消',
                    cancelColor: '#666666',
                    confirmText: '去设置',
                    confirmColor: '#3876ff',
                    success: (res) => {
                        if (res.confirm) {
                            // 打开小程序权限设置界面，重新授权
                            this.detectAuthorize();
                        } else if (res.cancel) {
                            // 点击取消，拒绝授权，退会处理
                            this.exitRoom('XYSDK:980403', '用户拒绝授权', null);
                        }
                    }
                });
            }
        },

        /**
         * 检测权限设置，如果没有授权，提示用户设置中授权操作
         *
         * @private
         */
        detectAuthorize() {
            try {
                uni.openSetting({
                    success: (res) => {
                        const camera = res.authSetting['scope.camera'];
                        const record = res.authSetting['scope.record'];
                        this.isShowDetected = false;
                        if (!camera || !record) {
                            console.log('pusher set permission: ', res);
                            this.detectAuthorizeModel();
                        } else {
                            console.log('pusher set permission success: ', res.authSetting);
                            this.exitRoom('XYSDK:980404', '手动授权成功，请重新入会', res.authSetting);
                        }
                    },
                    fail: (error) => {
                        console.warn('pusher set permission fail: ', error);
                        this.exitRoom('XYSDK:980405', '授权错误', error);
                    }
                });
            } catch (err) {
                console.warn('pusher set permission catch error: ', err);
            }
        }
    },
    watch: {
        muted: {
            handler: function (newVal) {
                this.mutedClone = JSON.parse(JSON.stringify(newVal));
                this.onSwitchAudioMute();
            },

            immediate: true
        },

        camera: {
            handler: function () {
                this.onSwitchVideoMute();
            },

            immediate: true
        },

        template: {
            handler: function (newVal) {
                this.templateClone = JSON.parse(JSON.stringify(this.template));
                console.log('<component> template change value: ', newVal);
                const detail = newVal ? newVal.detail : [];
                const layoutMode = newVal ? newVal.layout : 'auto';
                if (layoutMode === 'auto' && !this.XYClient) {
                    return;
                }
                this.XYClient.setLayoutMode(layoutMode).updateTemplate(detail);
            },

            immediate: true,
            deep: true
        }
    }
};
</script>
<style scoped>
.xy-room {
    position: fixed;
    left: 0;
    width: 100vw;
    background-color: red;
}

.xy-hide {
    display: none;
}

.xy__layout-container {
    height: 100vh;
    width: 100vw;
    color: #fff;
    position: relative;
    background-color: #1f1f25;
}

.xy__layout-container .video {
    position: fixed;
    width: 40vw;
    height: 40vh;
    left: 45vw;
    top: 0vh;
    right: 14vw;
    bottom: 25vh;
    overflow: hidden;
    vertical-align: top;
}

.xy__layout-container .video-player {
    width: 100%;
    height: 100%;
    position: relative;
}

.xy__layout-container .video-bg {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
    color: #fff;
    font-size: 16px;
    position: relative;
}

.xy__layout-container .video-cover-view {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
}

.xy__layout-container .video-pause-box {
    position: absolute;
    width: 100%;
    height: 100%;
    font-size: 12px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.video-pause-box .video-name {
    margin-bottom: 10px;
    width: 80%;
    text-align: center;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.xy__layout-container .video-pause-bg {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
}

.xy__layout-container .video-mute-icon {
    width: 14px;
    height: 14px;
    margin-right: 2px;
    flex-shrink: 0;
}

.xy__layout-container .video-member {
    font-size: 12px;
    max-width: 100%;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    border-radius: 2px;
}

.xy__layout-container .video-status {
    display: flex;
    align-items: center;
    max-width: 80%;
    padding: 2px 10px;
    font-size: 12px;
    background-color: rgba(0, 0, 0, 0.5);
    position: absolute;
    left: 0;
    bottom: 0;
    z-index: 101;
}

</style>
